#import "ALMainWindowController.h"
#import "ALAddEventSheetController.h"
#import "ALDetailViewController.h"
#import "ALDetailToolbarViewController.h"
#import "ALTemplateLibraryPopoverController.h"
#import "NSManagedObjectContext+SMRAdditions.h"
#import "ALModelHeaders.h"
#import "ALSourceListViewController.h"
#import "ALTemplateEditorPopoverController.h"
#import "NSObject+BlockObservation.h"
#import "ALInfoViewController.h"


#pragma mark Constants

NSString* const ALEntriesWereAddedNotification = @"ALEntriesWereAddedNotification";
//static NSString* const ALMainWindowCurrentVersionsKey = @"currentVersions";
static const CGFloat ALMainSplitViewMinPosition = 150.0f;
static const CGFloat ALMainSplitLeftViewMaxPercentage = 0.4f;  // Left side of main split can take no more than this percent of the view
static const CGFloat ALInfoSplitViewMinSize = 200.0f;
static const CGFloat ALInfoSplitViewMaxPercentage = 0.6f;  // Info view can take no more than this percent of detail view
static const CGFloat ALInfoViewDefaultWidth = 250.0f;


#pragma mark -
#pragma mark Class globals
static AMBlockToken *ALInfoViewHiddenToken;


#pragma mark -
@interface ALMainWindowController ()
@property (nonatomic, readonly) ALDetailViewController *detailViewController;
@property (nonatomic, readonly) ALInfoViewController *infoViewController;

-(ALDetailToolbarViewController *)detailToolbarViewController;
-(ALAddEventSheetController *)addEventSheetController;
-(void)addAppWithURL_:(NSURL *)url;
-(ALBundleType)bundleTypeForBundle_:(NSBundle *)aBundle;
-(ALApp *)appForBundle_:(NSBundle *)aBundle;
-(void)loadInfoPList_:(NSDictionary *)infoPList intoVersion:(ALVersion *)version;
-(NSArray *)entriesFromEntryTemplate_:(ALEntryTemplate *)entryTemplate withInfoPListObject:(id)infoPListObject;
-(ALEntry *)entryFromEntryTemplate_:(ALEntryTemplate *)entryTemplate withDictionary:(NSDictionary *)entryDictionary;
-(BOOL)arrayContainsDictionaries_:(NSArray *)array;
-(ALTemplateEditorPopoverController *)templateEditorPopoverController;
//-(void)positionInfoSplitViewWithAnimation_:(BOOL)shouldAnimate;
-(BOOL)loadInfoView_;
@end


#pragma mark -
@implementation ALMainWindowController

@synthesize mainSplitLeftView;
@synthesize mainSplitRightView;
@synthesize mainSplitView;
@synthesize sourceListViewController;
@synthesize modeSelectSegmentedControl;
//@synthesize currentVersionsArrayController;
@synthesize detailViewPlaceholder;
@synthesize detailToolbarViewPlaceholder;
@synthesize detailViewController;
@synthesize infoViewController;
@synthesize infoViewPlaceholder;
@synthesize infoSplitView;

@synthesize tempVersionChangeField;
@synthesize tempVersionChangeButton;


#pragma mark -
#pragma mark Initialization

-(void)awakeFromNib {
	[super awakeFromNib];
	
	NSView *detailPlaceholder = [self detailViewPlaceholder];
	NSView *detailView = [[self detailViewController] view];
	[detailView setFrame:[detailPlaceholder bounds]];
	[[self detailViewPlaceholder] addSubview:detailView];
//	NSView *detailParent = [detailPlaceholder superview];
//	[detailParent replaceSubview:detailPlaceholder with:detailView];
	
	NSView *toolbarPlaceholder = [self detailToolbarViewPlaceholder];
	NSView *toolbar = [[self detailToolbarViewController] view];
	[toolbar setFrame:[toolbarPlaceholder frame]];
	NSView *toolbarParent = [toolbarPlaceholder superview];
	[toolbarParent replaceSubview:toolbarPlaceholder with:toolbar];
    [[self detailToolbarViewController] setEntriesArrayController:[[self detailViewController] entriesArrayController]];
    
    AMBlockTask infoHiddenTask = ^(id object, NSDictionary *change) {
        if ([self loadInfoView_] && ALInfoViewHiddenToken)
            [[self infoViewPlaceholder] removeObserverWithBlockToken:ALInfoViewHiddenToken];
    };
    if (![self loadInfoView_])
        ALInfoViewHiddenToken = [[self infoViewPlaceholder] addObserverForKeyPath:@"hidden" task:infoHiddenTask];
}

-(BOOL)loadInfoView_ {
    if ([[self infoViewPlaceholder] isHidden])
        return NO;
    NSView *infoView = [[self infoViewController] view];
    NSRect infoRect = [[self infoViewPlaceholder] bounds];
    [infoView setFrame:infoRect];
    [[self infoViewPlaceholder] addSubview:infoView];
    return YES;
}

#pragma mark -
#pragma mark Accessors

-(ALDetailViewController *)detailViewController {
	if (!detailViewController) {
		detailViewController = [[ALDetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle] windowController:self managedObjectContext:[self managedObjectContext]];
		[self addViewController:detailViewController];
	}
	return detailViewController;
}

-(ALDetailToolbarViewController *)detailToolbarViewController {
	if (!detailToolbarViewController_) {
		detailToolbarViewController_ = [[ALDetailToolbarViewController alloc] initWithNibName:@"DetailToolbarView" bundle:[NSBundle mainBundle] windowController:self managedObjectContext:[self managedObjectContext]];
		[self addViewController:detailToolbarViewController_];
	}
	return detailToolbarViewController_;
}

-(ALAddEventSheetController *)addEventSheetController {
	if (!addEventSheetController_) {
		addEventSheetController_ = [[ALAddEventSheetController alloc] initWithWindowNibName:@"AddEventSheet" managedObjectContext:[self managedObjectContext]];
//		[addEventSheetController_ setSourceListColumnView:[self sourceList]];
	}
	return addEventSheetController_;
}

-(ALInfoViewController *)infoViewController {
    if (!infoViewController) {
        infoViewController = [[ALInfoViewController alloc] initWithNibName:@"InfoView" bundle:[NSBundle mainBundle] windowController:self];
        NSRect infoRect = [[self infoViewPlaceholder] bounds];
        NSView *infoView = [infoViewController view];
        [infoView setFrame:infoRect];
        [[self infoViewPlaceholder] addSubview:infoView];
    }
    return infoViewController;
}

+(NSSet *)keyPathsForValuesAffectingSelectedApp {
	return [NSSet setWithObject:@"sourceListViewController.selectedApp"];
}

-(ALApp *)selectedApp {
	return [[self sourceListViewController] selectedApp];
}

//-(NSPredicate *)currentVersionsFilterPredicate {
//	return [NSPredicate predicateWithFormat:@"currentApp != NIL"];
//}

//-(NSArray *)currentVersions {
//	return [[self currentVersionsArrayController] arrangedObjects];
//}


#pragma mark -
#pragma mark Template editor

-(void)displayTemplateEditorForObject:(ALEntryTemplate *)entryTemplate inView:(NSView *)targetView withRect:(NSRect)targetRect {
	[[self templateEditorPopoverController] displayPopoverForObject:entryTemplate inView:targetView withRect:targetRect];
}

-(ALTemplateEditorPopoverController *)templateEditorPopoverController {
	if (!templateEditorController_) {
		templateEditorController_ = [[ALTemplateEditorPopoverController alloc] initWithWindowNibName:@"TemplateEditorPopover" managedObjectContext:[self managedObjectContext]];
	}
	return templateEditorController_;
}


#pragma mark -
#pragma mark Actions

-(IBAction)newApps:(id)sender {
	NSOpenPanel *openPanel = [NSOpenPanel openPanel];
	[openPanel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger result) {
		if (result == NSFileHandlingPanelCancelButton)
			return;
		NSArray *urls = [openPanel URLs];
		for (NSURL *url in urls) {
			[self addAppWithURL_:url];
		}
	}];
}

-(IBAction)newEntry:(id)sender {
	[[self addEventSheetController] displaySheetInWindow:[self window]];
}

-(IBAction)addEventSheetEntries:(id)sender {
	NSArray *selectedTemplates = [[self addEventSheetController] selectedTemplates];
	if ([selectedTemplates count] == 0) {
		NSBeep();
		return;
	}
	ALVersion *currentVersion = [[self selectedApp] currentVersion];
	NSAssert(currentVersion != nil, @"ALMainWindowController:addEvent: No current version selected in the source list");
	NSMutableArray *addedEntries = [NSMutableArray arrayWithCapacity:[selectedTemplates count]];
	for (ALEntryTemplate *entryTemplate in selectedTemplates) {
		ALEntry *entry = [entryTemplate stamp];
		[addedEntries addObject:entry];
	}
	NSNotificationCenter *noteCenter = [NSNotificationCenter defaultCenter];
	[noteCenter postNotificationName:ALEntriesWereAddedNotification object:addedEntries];
}

-(IBAction)toggleInfoView:(id)sender {
    BOOL shouldCollapse = ![[self infoSplitView] isSubviewCollapsed:[self infoViewPlaceholder]];
    NSSize detailSize = [[self detailViewPlaceholder] frame].size;
    NSSize infoSize = [[self infoViewPlaceholder] frame].size;
    CGFloat totalWidth = [[self infoSplitView] bounds].size.width;
    if (shouldCollapse) {
        [[NSUserDefaults standardUserDefaults] setFloat:infoSize.width forKey:ALInfoViewLastWidthBeforeCloseKey];
        detailSize.width = totalWidth;
        infoSize.width = 0;
    }
    else {        
        CGFloat infoWidth = [[NSUserDefaults standardUserDefaults] floatForKey:ALInfoViewLastWidthBeforeCloseKey];
        CGFloat infoPosition = MAX(totalWidth - infoWidth, 0);
        CGFloat minPosition = [self splitView:[self infoSplitView] constrainMinCoordinate:infoPosition ofSubviewAt:0];
        CGFloat maxPosition = [self splitView:[self infoSplitView] constrainMaxCoordinate:infoPosition ofSubviewAt:0];
        infoPosition = MIN(infoPosition, maxPosition);
        infoPosition = MAX(infoPosition, minPosition);
        infoWidth = totalWidth - infoPosition - [[self infoSplitView] dividerThickness];
        detailSize.width -= infoWidth;
        infoSize.width = infoWidth;
        [[self infoViewPlaceholder] setHidden:NO];
    }
    [[[self detailViewPlaceholder] animator] setFrameSize:detailSize];
    [[[self infoViewPlaceholder] animator] setFrameSize:infoSize];
}


#pragma mark -
#pragma mark Window delegate

-(NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window {
    return [[self managedObjectContext] undoManager];
}


#pragma mark -
#pragma mark Split view delegate methods

-(BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)subview {
    if (splitView == [self mainSplitView] && subview == [self mainSplitRightView])
		return YES;
    if (splitView == [self infoSplitView] && subview == [self detailViewPlaceholder])
        return YES;
	return NO;
}

-(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMin ofSubviewAt:(NSInteger)dividerIndex {
    if (splitView == [self mainSplitView])
        return ALMainSplitViewMinPosition;
    // else infoSplitView
    CGFloat totalWidth = [splitView bounds].size.width;
    CGFloat maxInfoSize = totalWidth * ALInfoSplitViewMaxPercentage;
    CGFloat minPosition = totalWidth - maxInfoSize;
    return minPosition;
}

- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMax ofSubviewAt:(NSInteger)dividerIndex {
	CGFloat totalWidth = [splitView bounds].size.width;
    if (splitView == [self infoSplitView])
        return MAX(totalWidth - ALInfoSplitViewMinSize, 0);
    // else mainSplitView
	return totalWidth * ALMainSplitLeftViewMaxPercentage; 
}

-(BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview {
    if (splitView == [self infoSplitView] && subview == [self infoViewPlaceholder]) 
        return YES;
    return NO;
}

-(BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex {
    if (splitView == [self infoSplitView]) {
        return YES;
    }
    return NO;
}

-(void)splitViewDidResizeSubviews:(NSNotification *)aNotification {
    NSSplitView *splitView = [aNotification object];
    if (splitView == [self infoSplitView] && [[self infoViewPlaceholder] bounds].size.width == 0)
        [[self infoViewPlaceholder] setHidden:YES];
}


#pragma mark -
#pragma mark Private Methods

-(void)addAppWithURL_:(NSURL *)url {
	NSBundle *appBundle = [NSBundle bundleWithURL:url];
	if (!appBundle) {
		NSLog(@"not a bundle");
		return;
	}
	ALApp *app = [self appForBundle_:appBundle];
	ALVersion *newVersion = [ALVersion insertInManagedObjectContext:[self managedObjectContext]];
	NSDictionary *infoDictionary = [appBundle infoDictionary];
	NSString *iconFile = [infoDictionary valueForKey:@"CFBundleIconFile"];
	NSURL *iconURL = [appBundle URLForImageResource:iconFile];
	NSData *iconData = [NSData dataWithContentsOfURL:iconURL];
	NSString *version = [infoDictionary valueForKey:@"CFBundleShortVersionString"];
	if (!version)
		version = [infoDictionary valueForKey:@"CFBundleVersion"];

	[newVersion setApp:app];
	[newVersion setIcon:iconData];
	[newVersion setReleaseDate:[NSDate date]];
	[newVersion setVersionNumber:version];
	if (![app currentVersion] || [[[app currentVersion] versionNumber] localizedCaseInsensitiveCompare:[newVersion versionNumber]] == NSOrderedAscending)
		[app setCurrentVersion:newVersion];
	[self loadInfoPList_:infoDictionary intoVersion:newVersion];
}

-(ALApp *)appForBundle_:(NSBundle *)aBundle {
	NSDictionary *infoDictionary = [aBundle infoDictionary];
	NSString *bundleID = [infoDictionary valueForKey:@"CFBundleIdentifier"];
	NSArray *matchingApps = [[self managedObjectContext] smrFetchObjectsForEntityName:[ALApp entityName] withPredicate:@"bundleIdentifier == %@", bundleID];
	if ([matchingApps count] > 0)
		return [matchingApps objectAtIndex:0];
	ALApp *newApp = [ALApp insertInManagedObjectContext:[self managedObjectContext]];
	NSString *appTitle = [infoDictionary valueForKey:@"CFBundleName"];
	[newApp setTitle:appTitle];
	[newApp setBundleIdentifier:bundleID];
	ALBundleType appType = [self bundleTypeForBundle_:aBundle];
    [newApp setAppTypeValue:appType];
	return newApp;
}

-(void)loadInfoPList_:(NSDictionary *)infoPList intoVersion:(ALVersion *)version {
	for (NSString *key in infoPList) {
		ALEntryTemplate *entryTemplate = [[self managedObjectContext] smrFetchFirstObjectForEntityName:[ALEntryTemplate entityName] withPredicate:@"infoPListKey LIKE %@", key];
		if (!entryTemplate)
			continue;
		id infoPListObject = [infoPList valueForKey:key];
		NSArray *entries = [self entriesFromEntryTemplate_:entryTemplate withInfoPListObject:infoPListObject];
        [entries setValue:version forKey:@"version"];
	}
}

-(NSArray *)entriesFromEntryTemplate_:(ALEntryTemplate *)entryTemplate withInfoPListObject:(id)infoPListObject {
	if ([infoPListObject isKindOfClass:[NSDictionary class]]) {
		return [NSArray arrayWithObject:[self entryFromEntryTemplate_:entryTemplate withDictionary:infoPListObject]];
	}
	if ([infoPListObject isKindOfClass:[NSArray class]] && [self arrayContainsDictionaries_:infoPListObject]) {
		NSMutableArray *entries = [NSMutableArray array];
		for (NSDictionary *memberDictionary in infoPListObject) {
			[entries addObject:[self entryFromEntryTemplate_:entryTemplate withDictionary:memberDictionary]];
		}
		return entries;
	}
	if ([[entryTemplate elementTemplates] count] != 1) {
		NSLog(@"ALMainWindowController:entriesFromEntryTemplate_:withInfoPListObject: expected entry template with 1 element, but element count is %lu", [[entryTemplate elementTemplates] count]);
		return nil;
	}
	ALEntry *entry = [entryTemplate stamp];
	ALElement *element = [[entry elements] anyObject];
	[element setElementValue:infoPListObject];
	return [NSArray arrayWithObject:entry];
}

-(ALEntry *)entryFromEntryTemplate_:(ALEntryTemplate *)entryTemplate withDictionary:(NSDictionary *)entryDictionary {
	ALEntry *entry = [entryTemplate stamp];
	for (ALElement *element in [entry elements]) {
		NSString *elementKey = [element infoPListKey];
		id dictionaryObject = [entryDictionary valueForKey:elementKey];
		if (!dictionaryObject)
			continue;
		[element setElementValue:dictionaryObject];
	}
	return entry;
}

-(BOOL)arrayContainsDictionaries_:(NSArray *)array {
	for (id object in array) {
		if (![object isKindOfClass:[NSDictionary class]])
			return NO;
	}
	return YES;
}

-(NSString *)applicationSupportDirectory_ {
	
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
    NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
	NSString *appPath = [[NSBundle mainBundle] bundlePath];
	NSString *appName = [[appPath lastPathComponent] stringByDeletingPathExtension];
    return [basePath stringByAppendingPathComponent:appName];
}

-(void)createEntriesForVersion_:(ALVersion *)version fromBundle:(NSBundle *)aBundle {
	NSDictionary *infoDictionary = [aBundle infoDictionary];
	NSArray *entryTemplatesWithPListKey = [[self managedObjectContext] smrFetchObjectsForEntityName:[ALEntryTemplate entityName] withPredicate:@"infoPListKey != nil"];
	for (ALEntryTemplate *entryTemplate in entryTemplatesWithPListKey) {
		NSString *infoPListKey = [entryTemplate infoPListKey];
		id pListValue = [infoDictionary valueForKey:infoPListKey];
		if (!pListValue)
			continue;
//		ALEntry *newEntry = [self createEntryFromTemplate_:entryTemplate];
//		[newEntry 
	}
}

-(ALBundleType)bundleTypeForBundle_:(NSBundle *)aBundle {
	NSDictionary *infoDictionary = [aBundle infoDictionary];
	NSString *bundlePackageType = [infoDictionary valueForKey:@"CFBundlePackageType"];
	if ([bundlePackageType isEqualToString:@"FMWK"])
		return ALBundleTypeFramework;
	if ([bundlePackageType isEqualToString:@"BNDL"])
		return ALBundleTypeGenericBundle;
	if (![bundlePackageType isEqualToString:@"APPL"])
		return ALBundleTypeUnknown;
	BOOL iPhoneApp = [[infoDictionary valueForKey:@"LSRequiresIPhoneOS"] boolValue];
	if (!iPhoneApp)
		return ALBundleTypeMacApplication;
	id deviceTypes = [infoDictionary valueForKey:@"UIDeviceFamily"];
	if (!deviceTypes)
		return ALBundleTypePhoneApplication;
	if ([deviceTypes isKindOfClass:[NSArray class]]) // App supports multiple devices - this is universal app
		return ALBundleTypeUniversalPhoneApplication;
	NSInteger device = [(NSNumber *)deviceTypes integerValue];
	switch (device) {
		case 1:
			return ALBundleTypePhoneApplication;
		case 2:
			return ALBundleTypePadApplication;
		default:
			return ALBundleTypeUnknown;
	}
}



-(IBAction)tempChangeVersion:(id)sender {
	ALVersion *selectedVersion = [[self selectedApp] currentVersion];
	ALApp *selectedApp = [selectedVersion app];
	NSSet *versions = [selectedApp versions];
	NSString *newVersion = [[self tempVersionChangeField] stringValue];
	NSSet *filteredVersions = [versions filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"versionNumber LIKE %@", newVersion]];
	if ([filteredVersions count] == 0) {
		NSBeep();
		return;
	}
	[selectedApp setCurrentVersion:[filteredVersions anyObject]];
}

@end
